#include "../../core/pch.h"
#include "../../pi/BinarySemaphore.h"
#include "../../pi/Process.h"

unsigned int findCoordinatorIp(int ipg_port)
{
	int sock = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock == SOCKET_ERROR)
	{
		DEBUGC("find: error while creating socket (%s)", strerror(errno));
		exit(SOCKET_ERROR);
	}

	int broadcast = 1;
	int status = setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &broadcast, sizeof(broadcast));
	if (status == SETSOCKOPT_ERROR)
	{
		DEBUGC("find: error while setting socket option (%s)", strerror(errno));
		exit(SETSOCKOPT_ERROR);
	}

	struct sockaddr_in bcast_addr;
	memset(&bcast_addr, 0, sizeof(struct sockaddr_in)); 
	bcast_addr.sin_family = AF_INET; 
	bcast_addr.sin_port = htons(ipg_port); 
	bcast_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); 

	char msg = ARE_YOU_NUMFAC;
	unsigned int packedIp = 0;
	while (true)
	{
		sendto(sock, &msg, sizeof(char), 0, (struct sockaddr*) &bcast_addr, sizeof(bcast_addr));
		struct timeval tv;
		fd_set readfds;

		tv.tv_sec = 2;
		tv.tv_usec = 0;

		FD_ZERO(&readfds);
		FD_SET(sock, &readfds);

		DEBUGC("slave: looking for master computer");

		int ret = select(sock+1, &readfds, NULL, NULL, &tv);
		if (ret == SELECT_ERROR)
		{
			DEBUGC("find: error on select (%s)", strerror(errno));
			exit(SELECT_ERROR);
		}

		socklen_t len = sizeof(bcast_addr);
		if (FD_ISSET(sock, &readfds))
		{
			recvfrom(sock, &msg, sizeof(char), 0, (struct sockaddr*) &bcast_addr, &len);
			if (msg == I_AM_NUMFAC)
			{
				recvfrom(sock, &packedIp, sizeof(int), 0, (struct sockaddr*) &bcast_addr, &len);
				return packedIp;
			}
		}
		else
		{
			DEBUGS("slave: timed out while proobing for coordinator");
		}
	}
}

char* unpackIp(unsigned int ip)
{
	char* buf = (char*) malloc(sizeof(char) * 16);
	char* ptr = buf;
	memset(buf, 0, 16);

	sprintf(ptr, "%d", (ip & 0xff000000) >> 24);
	ptr[strlen(ptr)] = '.';
	ptr += strlen(ptr);
	sprintf(ptr, "%d", (ip & 0x00ff0000) >> 16);
	ptr[strlen(ptr)] = '.';
	ptr += strlen(ptr);
	sprintf(ptr, "%d", (ip & 0x0000ff00) >> 8);
	ptr[strlen(ptr)] = '.';
	ptr += strlen(ptr);
	sprintf(ptr, "%d", (ip & 0x000000ff));

	return buf;
}

/** Variable for statuses for sockets functions. */
int status;

int worker_port;
int logger_port;
int receiver_port;
int ipg_port;

/**
 * Semaphore for mutual exclusive access to 
 * logger_sock shared by slave and worker.
 */
int sem_id;

/** Options for slave process. */
struct option long_options[] =
{ 
	{"workport", required_argument, 0, 'a'},
	{"logport", required_argument, 0, 'b'},
	{"recvport", required_argument, 0, 'c'},
	{"ipgport", required_argument, 0, 'd'},
	{0, 0, 0, 0}
};

int main(int argc, char* argv[])
{
	if (argc < 9)
	{
		DEBUGC("Usage: slave --workport <port> --logport <port> --recvport <port> --ipgport <port>");
		exit(STATUS_ERR);
	}

	while (true)
	{
		int option_index = 0;
		int op = getopt_long(argc, argv, "a:b:c:d:", long_options, &option_index);
		if (op == NO_MORE_OPTIONS)
			break;

		switch (op)
		{
			case 'a':
				worker_port = atoi(optarg);
				break;
			case 'b':
				logger_port = atoi(optarg);
				break;
			case 'c':
				receiver_port = atoi(optarg);
				break;
			case 'd':
				ipg_port = atoi(optarg);
				break;
			default:
				exit(STATUS_ERR);
		}
	}

	/** Let's connect to logger. */
	unsigned int packedIp = findCoordinatorIp(ipg_port);
	FILL_SOCKADDR_IN(logger_addr, AF_INET, logger_port, unpackIp(packedIp));

	int logger_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (logger_sock == SOCKET_ERROR)
	{
		DEBUGC("slave: error while creating socket for logger (%s)", strerror(errno));
		exit(SOCKET_ERROR);
	}

	status = connect(logger_sock, (struct sockaddr*) &logger_addr, sizeof(logger_addr));
	if (status == CONNECT_ERROR)
	{
		DEBUGC("slave: error while connecting to logger (%s)", strerror(errno));
		exit(CONNECT_ERROR);
	}
	DEBUGC("slave: connected to logger, sending my pid to logger");

	/** Create and initialiaze semaphore for logger_sock. */
	BinarySemaphore::Create(sem_id);
	BinarySemaphore::Init(sem_id);

	/** Start worker process. */
	int worker_pid;
	if ((worker_pid = Process::Create()) == 0)
	{
		char* buf[] = { "worker", 0, 0, 0, 0, 0, 0 };
		buf[1] = (char*) malloc(sizeof(char) * 32);
		buf[2] = (char*) malloc(sizeof(char) * 32);
		buf[3] = (char*) malloc(sizeof(char) * 32);
		buf[4] = (char*) malloc(sizeof(char) * 32);
		buf[5] = (char*) malloc(sizeof(char) * 16);
		sprintf(buf[1], "%d", logger_sock);
		sprintf(buf[2], "%d", worker_port);
		sprintf(buf[3], "%d", sem_id);
		sprintf(buf[4], "%d", receiver_port);
		memset(buf[5], 0, 16);
		sprintf(buf[5], "%s", unpackIp(packedIp));
		execv("worker", buf);
	}
	
	/** Send pid to logger. */
	pid_t my_pid = getpid();
	send(logger_sock, &my_pid, sizeof(pid_t), 0);
	DEBUGC("slave: sent pid to logger");

	/** Send to logger worker port. */
	send(logger_sock, &worker_port, sizeof(int), 0);
	DEBUGC("slave: sent worker port to logger");

	char msg;
	while (true)
	{
		msg = ALIVE_MSG;
		BinarySemaphore::Decrement(sem_id);
		send(logger_sock, &msg, sizeof(char), 0);
		BinarySemaphore::Increment(sem_id);
		DEBUGC("slave: sent ALIVE_MSG, sleeping for %d s", TIME_FOR_IM_ALIVE_NEXT_MSG);

		sleep(TIME_FOR_IM_ALIVE_NEXT_MSG);
	}

	close(logger_sock);

	return 0;
}

